---
title: 'React Hot Toast'
tags: 'nextjs,react'
description: 'Current favorite lib for toast notifications, with some custom hook and implementation'
---

## React Hot Toast

A lib for toast notifications, my current favorite. Checkout [the documentation](https://react-hot-toast.com/docs/toast)

## Package Dependencies

```bash
yarn add react-hot-toast
```

## Set up

Add the div to `_app.ts` or `App.tsx`

```tsx
import { AppProps } from 'next/app';

import '@/styles/globals.css';

import DismissableToast from '@/components/DismissableToast';

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <>
      <DismissableToast />
      <Component {...pageProps} />
    </>
  );
}

export default MyApp;
```

### DismissableToast

React hot toast doesn't support dismiss button by default, so I use a custom component to add it.

```tsx
import * as React from 'react';
import { toast, ToastBar, Toaster } from 'react-hot-toast';
import { HiX } from 'react-icons/hi';

export default function DismissableToast() {
  return (
    <div>
      <Toaster
        reverseOrder={false}
        position='top-center'
        toastOptions={{
          style: {
            borderRadius: '8px',
            background: '#333',
            color: '#fff',
          },
        }}
      >
        {(t) => (
          <ToastBar toast={t}>
            {({ icon, message }) => (
              <>
                {icon}
                {message}
                {t.type !== 'loading' && (
                  <button
                    className='rounded-full p-1 ring-primary-400 transition hover:bg-[#444] focus:outline-none focus-visible:ring'
                    onClick={() => toast.dismiss(t.id)}
                  >
                    <HiX />
                  </button>
                )}
              </>
            )}
          </ToastBar>
        )}
      </Toaster>
    </div>
  );
}
```

## Usage

```tsx
import toast from 'react-hot-toast';

function addSomething() {
  toast.success('message');
  toast.error('message');
}
```

## Toast Promise Example

With promise, we don't need to use `toast.success` or `toast.error`, but directly send a promise, and it will be managed.

```tsx
toast.promise(
  axios
    .post('/user/login', data)
    .then((res) => {
      const { jwt: token } = res.data.data;
      tempToken = token;
      localStorage.setItem('token', token);

      // chaining axios in 1 promise
      return axios.get('/user/get-user-info');
    })
    .then((user) => {
      const role = user.data.data.user_role;
      dispatch('LOGIN', { ...user.data.data, token: tempToken });

      history.replace('/');
    }),
  {
    loading: 'Loading...',
    success: 'Success',
    error: (err) => err.response.data.msg,
  }
);
```

## Default Message

You can compose it with default message

```tsx
export const defaultToastMessage = {
  loading: 'Loading...',
  success: 'Data fetched successfully',
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  error: (err: any) =>
    err?.response?.data?.msg ?? 'Something is wrong, please try again',
};
```

## useLoadingToast hook

Using a `useLoadingToast` hook we can get access if there is any loading toast showing, then use it to disable button.

```tsx
import { useToasterStore } from 'react-hot-toast';

/**
 * Hook to get information whether something is loading
 * @returns true if there is a loading toast
 * @example const isLoading = useLoadingToast();
 */
export default function useLoadingToast(): boolean {
  const { toasts } = useToasterStore();
  const isLoading = toasts.some((toast) => toast.type === 'loading');
  return isLoading;
}
```

## useWithToast hook

This hook will handle loading and error state from SWR and show toast on initial fetch. Revalidating will not trigger loading toast.

```tsx
import * as React from 'react';
import toast from 'react-hot-toast';
import { SWRResponse } from 'swr';

import { defaultToastMessage } from '@/lib/helper';

import useLoadingToast from '@/hooks/useLoadingToast';

type OptionType = {
  runCondition?: boolean;
  loading?: string;
  success?: string;
  error?: string;
};

export default function useWithToast<T, E>(
  swr: SWRResponse<T, E>,
  { runCondition = true, ...customMessages }: OptionType = {}
) {
  const { data, error } = swr;

  const toastStatus = React.useRef<string>(data ? 'done' : 'idle');

  const toastMessage = {
    ...defaultToastMessage,
    ...customMessages,
  };

  React.useEffect(() => {
    if (!runCondition) return;

    // if toastStatus is done,
    // then it is not the first render or the data is already cached
    if (toastStatus.current === 'done') return;

    if (error) {
      toast.error(toastMessage.error, { id: toastStatus.current });
      toastStatus.current = 'done';
    } else if (data) {
      toast.success(toastMessage.success, { id: toastStatus.current });
      toastStatus.current = 'done';
    } else {
      toastStatus.current = toast.loading(toastMessage.loading);
    }

    return () => {
      toast.dismiss(toastStatus.current);
    };
  }, [
    data,
    error,
    runCondition,
    toastMessage.error,
    toastMessage.loading,
    toastMessage.success,
  ]);

  return { ...swr, isLoading: useLoadingToast() };
}
```

### Usage

You can use it with the `useSWR` hook

```tsx
const { data: pokemonData, isLoading } = useWithToast(
  useSWR<PokemonList>('https://pokeapi.co/api/v2/pokemon?limit=20'),
  {
    loading: 'Override Loading',
  }
);
```
